const pCamStream = navigator.mediaDevices.getUserMedia({ audio: true, video: true });
pCamStream.then(stream => camStream = stream);

class Client {
	constructor(name, room) {
		this.name = name;
		this.room = room;

		this.socket = null;

		this.localVideo = document.createElement('video');
		this.localVideo.autoplay = true;
		this.localVideo.controls = false;
		this.localVideo.muted = true;

		this.remoteVideo = document.createElement('video');
		this.remoteVideo.autoplay = true;
		this.remoteVideo.controls = false;

		this.dataChannel = null;
		this.peerConnection = new RTCPeerConnection();
		this.peerConnection.addEventListener('connectionstatechange', e => this.onconnectionstatechange(e));
		this.peerConnection.addEventListener('iceconnectionstatechange', e => this.oniceconnectionstatechange(e));
		this.peerConnection.addEventListener('icecandidate', e => this.onicecandidate(e));
		this.peerConnection.addEventListener('track', e => this.onremotetrack(e));
		this.peerConnection.addEventListener('datachannel', e => this.onremotedatachannel(e));

		this.waitBind = pCamStream.then(stream => {
			this.localVideo.srcObject = stream;
			if (this.peerConnection.addTrack) {
				stream.getTracks().forEach(track => {
					this.peerConnection.addTrack(track, stream);
				});
			} else {
				this.peerConnection.addStream(stream);
			}
		});

		this.connect();
		this.log('created');
	}

	connect() {
		this.socket = io.connect();
		this.socket.emit('join', this.room);
		this.socket.on('data', data => this.onReceiveData(data));
	}

	sendData(data) {
		if (this.room) {
			data.room = this.room;
			this.socket.emit('send', data);
		}
	}

	onReceiveData(data) {
		const { room, type, payload } = data;
		if (room !== this.room) {
			return;
		}

		switch (type) {
			case 'join':
				this.call();
				break;
			case 'candidate':
				this.onremoteicecandidate(payload);
				break;
			case 'offer':
				this.onremoteoffersdp(payload);
				break;
			case 'answer':
				this.onremoteanswersdp(payload);
				break;
		}
	}

	onconnectionstatechange(e) {
		this.log('connection state change', this.peerConnection.connectionState, e);
	}

	oniceconnectionstatechange(e) {
		this.log('ice connection state change', this.peerConnection.iceConnectionState, e);
	}

	onicecandidate(e) {
		this.log('self ice candidate', e.candidate);
		this.sendData({ type: 'candidate', payload: e.candidate });
	}

	onremotetrack(e) {
		this.log('got remote track');
		const stream = e.streams[0];
		if (this.remoteVideo.srcObject !== stream) {
			this.remoteVideo.srcObject = stream;
		}
	}

	onremoteicecandidate(data) {
		this.log('got remote ice candidate', data);
		if (data) {
			const candidate = new RTCIceCandidate(data);
			this.peerConnection.addIceCandidate(candidate);
		}
	}

	onremotedatachannel(e) {
		this.dataChannel = e.channel;
		this.dataChannel.addEventListener('message', e => this.onchannelmessage(e));
	}

	async onremoteoffersdp(data) {
		this.log('got remote sdp offer', data);
		const sd = new RTCSessionDescription(data);
		this.peerConnection.setRemoteDescription(sd);

		await this.waitBind;
		const desc = await this.peerConnection.createAnswer();
		this.peerConnection.setLocalDescription(desc);
		this.log('send sdp answer', desc);
		this.sendData({ type: 'answer', payload: desc });
	}

	onremoteanswersdp(data) {
		this.log('got remote sdp answer', data);
		const sd = new RTCSessionDescription(data);
		this.peerConnection.setRemoteDescription(sd);
	}

	onchannelmessage(e) {
		this.log('receive data channel message', e.data);
	}

	sendChannelMessage(msg) {
		if (this.dataChannel) {
			this.dataChannel.send(msg);
		}
	}

	async call() {
		this.log('start call');

		this.dataChannel = this.peerConnection.createDataChannel("");
		this.dataChannel.addEventListener('message', e => this.onchannelmessage(e));

		await this.waitBind;
		const desc = await this.peerConnection.createOffer();
		this.peerConnection.setLocalDescription(desc);
		this.log('send sdp offer', desc);
		this.sendData({ type: 'offer', payload: desc });
	}

	destroy() {
		this.socket.disconnect();
		this.socket = null;
		this.peerConnection.close();
		this.peerConnection = null;
		this.localVideo.srcObject = null;
		this.localVideo = null;
		this.remoteVideo.srcObject = null;
		this.remoteVideo = null;
		this.stream = null;
	}

	log(...messages) {
		console.log(`[${this.name}]`, ...messages);
	}
}
